/**
 * Copyright(c) 2013 Jade Techonologies Co., Ltd.
 *
 * History:
 *   13-4-17 下午1:16 Created by dwzhang
 */
package com.jade.framework.base.criterion.sql;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import com.jade.framework.base.criterion.*;
import com.jade.framework.base.criterion.expression.*;
import com.jade.framework.base.util.PagingArrayList;
import com.jade.framework.base.util.PagingList;

/**
 * SQL查询条件工具类。
 *
 * @author dwzhang
 * @version 1.0 13-4-17 下午1:16
 */
public class SqlUtils
{
    protected static Map<Class, CriterionInterpreter> map = new HashMap<Class, CriterionInterpreter>();

    static {
        map.put(BetweenExpression.class, new BetweenInterpreter());
        map.put(LikeExpression.class, new LikeInterpreter());
        map.put(InExpression.class, new InInterpreter());
        map.put(NotInExpression.class, new InInterpreter(true));
        map.put(NullExpression.class, new NullInterpreter());
        map.put(NotNullExpression.class, new NotNullInterpreter());
        map.put(ConjunctionExpression.class, new ConjunctionInterpreter());
        map.put(NotExpression.class, new NotInterpreter());
        map.put(AndExpression.class, new LogicalInterpreter());
        map.put(OrExpression.class, new LogicalInterpreter());
        map.put(CompareExpression.class, new CompareInterpreter());
        map.put(ReverseInExpression.class, new ReverseInInterpreter());
        map.put(Order.class, new OrderInterpreter());
        map.put(QueryEntity.class, new InjectEntityInterpreter());
        map.put(Group.class, new GroupInterpreter());
        map.put(Having.class, new HavingInterpreter());
        map.put(Boolean.class, new DistinctInterpreter());
        map.put(Criteria.class, new CriteriaInterpreter());
    }

    @SuppressWarnings (value = "unchecked")
    public static void interprete(SqlContext context, Object obj)
    {
        CriterionInterpreter<SqlContext, Object> interpreter = map.get(obj.getClass());
        if (interpreter == null) {
            throw new IllegalArgumentException("Unsupported class:" + obj.getClass().getName());
        }
        interpreter.interprete(context, obj);
    }

    public static void interpreteSql(SqlContext context, List<Criterion> criterions)
    {
        String upperSql = context.getContextString().toUpperCase();
        int index = upperSql.indexOf(Constants.WHERE);

        if (index == -1 && (criterions.size() > 0)) {
            assembleSqlContext(context, Constants.WHERE);
        }

        for (int i = 0; i < criterions.size(); i++) {
            if (i == 0 && index > 0) {
                if (index + Constants.WHERE.length() + 4 < upperSql.length()) {
                    //Need add AND
                    assembleSqlContext(context, Constants.AND);
                }
            }
            else if (i > 0) {
                assembleSqlContext(context, Constants.AND);
            }
            SqlUtils.interprete(context, criterions.get(i));
        }
    }

    /**
     * 一种[" "+$+" "]的组装方法.
     *
     * @param context 上下文
     * @param middle  中间的字符串
     */
    public static void assembleSqlContext(SqlContext context, String middle)
    {
        context.append(Constants.BLANK);
        context.append(middle);
        context.append(Constants.BLANK);
    }

    public static String interpreteSql(String sql, Criteria criteria)
    {
        SqlContext context = new SqlContext(sql);
        interprete(context, criteria);
        return context.getContextString();
    }

    /**
     * SQL查询的util方法.
     * 可以简化进行sql查询的代码量,类似于template模式的思想,只不过使用了组合的方法.
     *
     * @param sql           SQL查询语句
     * @param entityManager entityManager
     * @param criteria      查询条件
     * @return 查询出来的对象列表
     */
    @SuppressWarnings (value = "unchecked")
    public static <T> PagingList<T> sqlQuery(String sql, EntityManager entityManager, Criteria criteria)
    {
        return sqlQuery(sql, entityManager, criteria, false);
    }

    public static <T> PagingList<T> sqlQuery(String sql, EntityManager entityManager, Criteria criteria,
            boolean isNative)
    {
        if (criteria == null) {
            criteria = new Criteria();
        }
        SqlContext context = new SqlContext(sql);
        interprete(context, criteria);
        sql = context.getContextString();

        PagingList<T> result;
        Query query;
        int count = 0;
        int offset = criteria.getOffset();
        int length = criteria.getLength();

        if (offset > -1 && length > -1) {
            String countSql = getCountSqlString(sql, false);
            query = isNative ? entityManager.createNativeQuery(countSql) : entityManager.createQuery(countSql);
            List preparedValues = context.getPreparedValues();
            fillQuery(query, preparedValues.toArray(new Object[preparedValues.size()]));
            count = ((Number)query.getSingleResult()).intValue();
            if (count == 0) {
                return new PagingArrayList<T>(0, new ArrayList<T>());
            }
        }

        query = isNative ? entityManager.createNativeQuery(sql) : entityManager.createQuery(sql);
        List preparedValues = context.getPreparedValues();
        fillQuery(query, preparedValues.toArray(new Object[preparedValues.size()]));
        if (offset > -1) {
            query.setFirstResult(offset);
        }
        if (length > -1) {
            query.setMaxResults(length);
        }

        List<T> list = (List<T>)query.getResultList();

        if (count == 0) {
            count = list.size();
        }
        result = new PagingArrayList<T>(count, list);
        return result;
    }

    /**
     * SQL查询的util方法.
     * 可以简化进行sql查询的代码量,类似于template模式的思想,只不过使用了组合的方法.
     *
     * @param sql           SQL查询语句
     * @param entityManager entityManager
     * @param criteria      查询条件
     * @return 查询出来的对象列表
     */
    @SuppressWarnings (value = "unchecked")
    public static <T> List<T> sqlQueryWithoutCount(String sql, EntityManager entityManager, Criteria criteria)
    {
        return sqlQueryWithoutCount(sql, entityManager, criteria, false);
    }

    public static <T> List<T> sqlQueryWithoutCount(String sql, EntityManager entityManager, Criteria criteria,
            boolean isNative)
    {
        if (criteria == null) {
            criteria = new Criteria();
        }
        SqlContext context = new SqlContext(sql);
        interprete(context, criteria);
        sql = context.getContextString();

        Query query = isNative ? entityManager.createNativeQuery(sql) : entityManager.createQuery(sql);
        List preparedValues = context.getPreparedValues();
        fillQuery(query, preparedValues.toArray(new Object[preparedValues.size()]));

        int offset = criteria.getOffset();
        int length = criteria.getLength();

        if (offset > -1) {
            query.setFirstResult(offset);
        }
        if (length > -1) {
            query.setMaxResults(length);
        }

        return (List<T>)query.getResultList();
    }

    @SuppressWarnings (value = "unchecked")
    public static <T> T getSingleResult(Query query)
    {
        List<T> objects = query.getResultList();
        if (objects.isEmpty()) {
            return null;
        }
        else {
            return objects.get(0);
        }
    }


    /**
     * 获取统计的结果数count.
     *
     * @param sql      SQL字符串
     * @param distinct 是否允许重复，默认为false
     * @return 统计结果的sql
     */
    public static String getCountSqlString(String sql, boolean distinct)
    {
        StringBuilder buf = new StringBuilder("SELECT ");
        if (distinct) {
            buf.append("DISTINCT ");
        }
        buf.append("COUNT(*) ");

        String temp = sql.toUpperCase();
        int begin = temp.indexOf("FROM");
        int middle = temp.indexOf(" FETCH ");
        int end = temp.indexOf("ORDER BY");

        if (begin > -1) {
            //包含from关键字
            if (middle > -1 && middle > begin) {
                //包含fetch关键字
                buf.append(sql.substring(begin, middle));
                if (end > middle) {
                    //包含order by关键字
                    buf.append(sql.substring(middle + " FETCH".length(), end));
                }
                else {
                    buf.append(sql.substring(middle + " FETCH".length()));
                }
            }
            else {
                if (end > begin) {
                    //包含order by关键字
                    buf.append(sql.substring(begin, end));
                }
                else {
                    buf.append(sql.substring(begin));
                }
            }
        }
        return buf.toString();
    }

    /**
     * 获取统计的结果数count，不区分重复记录.
     *
     * @param sql SQL字符串
     * @return 统计结果的sql
     */
    public static String getCountSqlString(String sql)
    {
        return getCountSqlString(sql, false);
    }

    public static void fillQuery(Query query, Object... params)
    {
        if (params.length == 0) {
            return;
        }
        int i = 0;
        for (Object obj : params) {
            query.setParameter(++i, obj);
        }
    }
}
